﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Chromos.Actors;
using Chromos.AIEngine;

namespace Chromos.ActionEngine
{
	public enum ActionType
	{
		Idle,
		Attack, 
		Defence,
		Movement,
        Death
	}

	/// <summary>
	/// Engine that handles all animations for actors
	/// </summary>
	public class ActorActionEngine
	{
		private List<Action> idles = new List<Action>(2);
		private List<AttackAction> attacks = new List<AttackAction>(2);
		private List<Action> defences = new List<Action>(2);
		private List<Action> movements = new List<Action>(2);
        private List<Action> deaths = new List<Action>(2);
		private List<Action> currentList = new List<Action>(2);
		private Actor actor;
		private int currentAction;
		private Texture2D currentImage;
		private int lastAttacked = 0;

		public ActorActionEngine(Actor actor)
		{
			this.actor = actor;
			setActionListIndex(0);
		}

		/// <summary>
		/// Loads all the images in the lists
		/// </summary>
		public void load()
		{
			foreach (Action a in idles)
			{
				a.load();
			}
			foreach (Action a in attacks)
			{
				a.load();
			}
			foreach (Action a in defences)
			{
				a.load();
			}
			foreach (Action a in movements)
			{
				a.load();
			}
            foreach (Action a in deaths)
            {
                a.load();
            }
		}

		/// <summary>
		/// Will play the action indicated by the given list and offset
		/// </summary>
		/// <param name="a"></param>
		/// <param name="listIndex"></param>
		public void playAction(ActionType a, int listIndex)
		{
			switch (a)
			{
				case ActionType.Idle:
					currentList.Insert(0, idles.ElementAt(listIndex));
					idles.ElementAt(listIndex).start();
					break;
				case ActionType.Attack:
					if (lastAttacked > attacks.ElementAt(listIndex).rate)
					{
						actor.getAIEngine().setAttacking();
						lastAttacked = 0;
						currentList.Add(attacks.ElementAt(listIndex));
						attacks.ElementAt(listIndex).start(listIndex);
					}
					break;
				case ActionType.Defence:
					currentList.Add(defences.ElementAt(listIndex));
					defences.ElementAt(listIndex).start();
					break;
				case ActionType.Movement:
					currentList.Insert(1, movements.ElementAt(listIndex));
					movements.ElementAt(listIndex).start();
					break;
                case ActionType.Death:
					try
					{
						currentList.Add(deaths.ElementAt(listIndex));
						deaths.ElementAt(listIndex).start();
					}
					catch (Exception e)
					{
						Console.WriteLine("The actor " + this.actor.ToString() + " needs a death action");
					}
                    break;
			}
		}
		/// <summary>
		/// Removes the specified action from the action list
		/// </summary>
		/// <param name="a"></param>
		/// <param name="listIndex"></param>
		public void stopAction(ActionType a, int listIndex)
		{
			switch (a)
			{
				case ActionType.Idle:
					currentList.Remove(idles.ElementAt(listIndex));
					idles.ElementAt(listIndex).stop();
					break;
				case ActionType.Attack:
					currentList.Remove(attacks.ElementAt(listIndex));
					attacks.ElementAt(listIndex).stop();
					break;
				case ActionType.Defence:
					currentList.Remove(defences.ElementAt(listIndex));
					defences.ElementAt(listIndex).stop();
					break;
				case ActionType.Movement:
					currentList.Remove(movements.ElementAt(listIndex));
					movements.ElementAt(listIndex).stop();
					break;
                case ActionType.Death:
                    currentList.Remove(deaths.ElementAt(listIndex));
                    deaths.ElementAt(listIndex).stop();
                    break;
			}
		}
		/// <summary>
		/// Stops all actions except ones which go on when the player is idle, such as attacks and jumps
		/// </summary>
		public void stopAllActions()
		{
			Action a;
			for(int i = currentList.Count-1; i >= 0; i--){
				a = currentList.ElementAt(i);
				if (!(a is AttackAction) && !(a is JumpAction) && !(a is IdleAction))
				{
					a.stop();
					currentList.RemoveAt(i);
				}
			}
		}

		/// <summary>
		/// Stops all actions without unapplying the force
		/// </summary>
		public void stopAllActionsWithForce()
		{
			Action a;
			for (int i = currentList.Count - 1; i >= 0; i--)
			{
				a = currentList.ElementAt(i);
				if (!(a is AttackAction) && !(a is JumpAction) && !(a is IdleAction))
				{
					a.stopWithForce();
					currentList.RemoveAt(i);
				}
			}
		}

		public void addIdle(Action a)
		{
			idles.Add(a);
		}
		public void addAttack(AttackAction a)
		{
			attacks.Add(a);
		}
		public void addDefence(Action a)
		{
			defences.Add(a);
		}
		public void addMovement(Action a)
		{
			movements.Add(a);
		}
        public void addDeath(Action a)
        {
            deaths.Add(a);
        }

		public List<Action> getIdles()
		{
			return idles;
		}
		public List<AttackAction> getAttacks()
		{
			return attacks;
		}
		public List<Action> getDefences()
		{
			return defences;
		}
		public List<Action> getMovements()
		{
			return movements;
		}
        public List<Action> getDeaths()
        {
            return deaths;
        }

		/// <summary>
		/// Returns a vector2 representing the location of the action in our lists. First component is the list (ActionType), second is the index
		/// </summary>
		/// <param name="a"></param>
		/// <returns></returns>
		public int[] findAction(Action a)
		{
			int[] v = new int[2];
			v[0] = (int)ActionType.Attack;
			if(a is AttackAction && (v[1] = attacks.IndexOf((AttackAction)a)) != -1){
				return v;
			}

			v[0] = (int)ActionType.Death;
			if((v[1] = deaths.IndexOf(a)) != -1){
				return v;
			}

			v[0] = (int)ActionType.Defence;
			if ((v[1] = defences.IndexOf(a)) != -1)
			{
				return v;
			}

			v[0] = (int)ActionType.Idle;
			if ((v[1] = idles.IndexOf(a)) != -1)
			{
				return v;
			}

			v[0] = (int)ActionType.Movement;
			if ((v[1] = movements.IndexOf(a)) != -1)
			{
				return v;
			}

			return null;
		}

		/// <summary>
		/// Sets the index of the element in any list of actions that will be used
		/// Be sure to set this before calling setIdle or anything similar
		/// </summary>
		/// <param name="i"></param>
		public void setActionListIndex(int i)
		{
			currentAction = i;
		}

		public Texture2D getCurrentImage()
		{
			if (currentImage == null)
				currentImage = ((VisualAction)idles.ElementAt(0)).getCurrentImage();
			return currentImage;
		}

		/// <summary>
		/// Performs non-instantaneous value updates, and animates
		/// </summary>
		public void update()
		{
			lastAttacked += actor.game.timeSinceLastUpdate;
			act();
		}

		private void act()
		{
			int[] actType;
            for (int i = 0; i < currentList.Count; i++)
			{
                Action a = currentList.ElementAt(i);
				if(a is VisualNonLoopingAction && !((VisualNonLoopingAction)a).isFinished() || !(a is VisualNonLoopingAction))
					a.act();

                if (a is VisualAction)
                {
                    currentImage = ((VisualAction)a).getCurrentImage();
                }
				if (a is VisualNonLoopingAction && ((VisualNonLoopingAction)a).isFinished() && !(a is DeathAction))
				{
					actType = findAction(a);
					stopAction((Chromos.ActionEngine.ActionType)actType[0], actType[1]);
					if (a is AttackAction)
					{
						if (actor.getFacing() == FACING.LEFT)
							actor.getAIEngine().setMovingLeft();
						else
							actor.getAIEngine().setMovingRight();
					}
				}

				if (a is DeathAction && ((VisualNonLoopingAction)a).isFinished())
				{
					// Play secondary death animation if we hit the ground
					if (deaths.Count > 1 && actor.hasGrounded)
					{
						this.playAction(ActionType.Death, 1);
						actor.hasGrounded = false;
					}
					actor.getAIEngine().setDead();
					actor.applyGravity = true;
				}
            }
		}
	}
}
